import _ from './under-dash';
import colCache from './col-cache';

class CellMatrix {
    constructor(template) {
        this.template = template;
        this.sheets = {};
    }

    addCell(addressStr) {
        this.addCellEx(colCache.decodeEx(addressStr));
    }

    getCell(addressStr) {
        return this.findCellEx(colCache.decodeEx(addressStr), true);
    }

    findCell(addressStr) {
        return this.findCellEx(colCache.decodeEx(addressStr), false);
    }

    findCellAt(sheetName, rowNumber, colNumber) {
        const sheet = this.sheets[sheetName];
        const row = sheet && sheet[rowNumber];
        return row && row[colNumber];
    }

    addCellEx(address) {
        if (address.top) {
            for (let row = address.top; row <= address.bottom; row++) {
                for (let col = address.left; col <= address.right; col++) {
                    this.getCellAt(address.sheetName, row, col);
                }
            }
        } else {
            this.findCellEx(address, true);
        }
    }

    getCellEx(address) {
        return this.findCellEx(address, true);
    }

    findCellEx(address, create) {
        const sheet = this.findSheet(address, create);
        const row = this.findSheetRow(sheet, address, create);
        return this.findRowCell(row, address, create);
    }

    getCellAt(sheetName, rowNumber, colNumber) {
        const sheet = this.sheets[sheetName] || (this.sheets[sheetName] = []);
        const row = sheet[rowNumber] || (sheet[rowNumber] = []);
        const cell =
            row[colNumber] ||
                (row[colNumber] = {
                    sheetName,
                    address: colCache.n2l(colNumber) + rowNumber,
                    row: rowNumber,
                    col: colNumber,
                });
        return cell;
    }

    removeCellEx(address) {
        const sheet = this.findSheet(address);
        if (!sheet) {
            return;
        }
        const row = this.findSheetRow(sheet, address);
        if (!row) {
            return;
        }
        delete row[address.col];
    }

    forEachInSheet(sheetName, callback) {
        const sheet = this.sheets[sheetName];
        if (sheet) {
            sheet.forEach((row, rowNumber) => {
                if (row) {
                    row.forEach((cell, colNumber) => {
                        if (cell) {
                            callback(cell, rowNumber, colNumber);
                        }
                    });
                }
            });
        }
    }

    forEach(callback) {
        _.each(this.sheets, (sheet, sheetName) => {
            this.forEachInSheet(sheetName, callback);
        });
    }

    map(callback) {
        const results = [];
        this.forEach(cell => {
            results.push(callback(cell));
        });
        return results;
    }

    findSheet(address, create) {
        const name = address.sheetName;
        if (this.sheets[name]) {
            return this.sheets[name];
        }
        if (create) {
            return (this.sheets[name] = []);
        }
        return undefined;
    }

    findSheetRow(sheet, address, create) {
        const { row } = address;
        if (sheet && sheet[row]) {
            return sheet[row];
        }
        if (create) {
            return (sheet[row] = []);
        }
        return undefined;
    }

    findRowCell(row, address, create) {
        const { col } = address;
        if (row && row[col]) {
            return row[col];
        }
        if (create) {
            return (row[col] = this.template
                ? Object.assign(address, JSON.parse(JSON.stringify(this.template)))
                : address);
        }
        return undefined;
    }

    spliceRows(sheetName, start, numDelete, numInsert) {
        const sheet = this.sheets[sheetName];
        if (sheet) {
            const inserts = [];
            for (let i = 0; i < numInsert; i++) {
                inserts.push([]);
            }
            sheet.splice(start, numDelete, ...inserts);
        }
    }

    spliceColumns(sheetName, start, numDelete, numInsert) {
        const sheet = this.sheets[sheetName];
        if (sheet) {
            const inserts = [];
            for (let i = 0; i < numInsert; i++) {
                inserts.push(null);
            }
            _.each(sheet, row => {
                row.splice(start, numDelete, ...inserts);
            });
        }
    }
}

export default CellMatrix;
